/**
 *                            License
 * THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS  
 * CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). 
 * THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW.  
 * ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR  
 * COPYRIGHT LAW IS PROHIBITED.
 * 
 * BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND  
 * AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE  
 * MAY BE CONSIDERED TO BE A CONTRACT,
 * THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED 
 * HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
 * 
 */
package com.lineage.server.utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.lineage.server.utils.collections.Maps;

/**
 * Class <code>LeakCheckConnection</code> 检查数据库内存泄漏.
 * 
 * @author jrwz
 * @version 2012-5-23下午11:29:49
 * @see com.lineage.server.utils
 * @since JDK1.7
 */
public final class LeakCheckConnection {

    /**
     * 连接处理程序.
     */
    private class ConnectionHandler implements InvocationHandler {
        /** 连接处理程序. */
        public ConnectionHandler() {
            // TODO Auto-generated constructor stub
        }

        @Override
        public Object invoke(final Object proxy, final Method method,
                final Object[] args) throws Throwable {
            if (method.getName().equals("close")) {
                LeakCheckConnection.this.closeAll();
            }
            Object o = LeakCheckConnection.this.send(
                    LeakCheckConnection.this._con, method, args);
            if (o instanceof Statement) {
                LeakCheckConnection.this._openedStatements.put((Statement) o,
                        new Throwable());
                o = new Delegate(o, PreparedStatement.class)._delegateProxy;
            }
            return o;
        }
    }

    /**
     * 转.
     */
    private class Delegate implements InvocationHandler {
        /** . */
        final Object _delegateProxy;
        /** . */
        private final Object _original;

        /** . */
        Delegate(final Object o, final Class<?> c) {
            this._original = o;
            this._delegateProxy = Proxy.newProxyInstance(c.getClassLoader(),
                    new Class[] { c }, this);
        }

        @Override
        public Object invoke(final Object proxy, final Method method,
                final Object[] args) throws Throwable {
            if (method.getName().equals("close")) {
                LeakCheckConnection.this.remove(this._original);
            }
            Object o = LeakCheckConnection.this.send(this._original, method,
                    args);
            if (o instanceof ResultSet) {
                LeakCheckConnection.this._openedResultSets.put((ResultSet) o,
                        new Throwable());
                o = new Delegate(o, ResultSet.class)._delegateProxy;
            }
            return o;
        }
    }

    /** 提示信息. */
    private static final Logger LOG = Logger
            .getLogger(LeakCheckConnection.class.getName());

    /**
     * 创建.
     * 
     * @param con
     *            与特定数据库的连接(Connection)
     * @return 检查
     */
    public static Connection create(final Connection con) {
        return (Connection) new LeakCheckConnection(con)._proxy;
    }

    /** 与特定数据库的连接(Connection). */
    private final Connection _con;
    /** Statement集合. */
    private final Map<Statement, Throwable> _openedStatements = Maps.newMap();
    /** ResultSet集合. */
    private final Map<ResultSet, Throwable> _openedResultSets = Maps.newMap();
    /** . */
    private final Object _proxy;

    /**
     * 检查数据库内存泄漏.
     * 
     * @param con
     *            Connection连接
     */
    private LeakCheckConnection(final Connection con) {
        this._con = con;
        this._proxy = Proxy.newProxyInstance(Connection.class.getClassLoader(),
                new Class[] { Connection.class }, new ConnectionHandler());
    }

    /**
     * 释放所有资源.
     */
    void closeAll() {
        if (!this._openedResultSets.isEmpty()) {
            for (final Throwable t : this._openedResultSets.values()) {
                LOG.log(Level.WARNING, "检测到内存泄漏: ResultSets.", t);
            }
        }
        if (!this._openedStatements.isEmpty()) {
            for (final Throwable t : this._openedStatements.values()) {
                LOG.log(Level.WARNING, "检测到内存泄漏: Statement.", t);
            }
        }
        for (final ResultSet rs : this._openedResultSets.keySet()) {
            SQLUtil.close(rs);
        }
        for (final Statement ps : this._openedStatements.keySet()) {
            SQLUtil.close(ps);
        }
    }

    /**
     * 删除.
     * 
     * @param o
     *            对象
     */
    void remove(final Object o) {
        if (o instanceof ResultSet) {
            this._openedResultSets.remove(o);
        } else if (o instanceof Statement) {
            this._openedStatements.remove(o);
        } else {
            throw new IllegalArgumentException("bad class:" + o);
        }
    }

    /**
     * 发送.
     * 
     * @param o
     *            对象
     * @param m
     *            单独的某个方法
     * @param args
     *            参数
     * @throws Throwable
     *             Throwable 类是 Java 语言中所有错误或异常的超类。<br>
     *             只有当对象是此类（或其子类之一）的实例时，才能通过 Java 虚拟机或者 Java throw 语句抛出。<br>
     *             类似地，只有此类或其子类之一才可以是 catch 子句中的参数类型。
     * @return 由调用方法或构造中抛出的异常
     */
    Object send(final Object o, final Method m, final Object[] args)
            throws Throwable {
        try {
            return m.invoke(o, args);
        } catch (final InvocationTargetException e) {
            if (e.getCause() != null) {
                throw e.getCause();
            }
            throw e;
        }
    }
}
